Files
Operator 0799bfbae8 Initial public portfolio release
Sanitized version of red team infrastructure automation platform.
Operational content (implant pipelines, lures, credential capture)
replaced with documented stubs. Architecture and infrastructure
automation code intact.
2026-06-23 16:12:14 -04:00

465 lines
19 KiB
YAML

---
# AWS Cleanup Playbook - Comprehensive version with robust VPC removal
- name: Clean up AWS resources
hosts: localhost
gather_facts: false
connection: local
vars_files:
- vars.yaml
vars:
aws_region: "{{ aws_region | default(aws_region_choices | random) }}"
confirm_cleanup: false # Skip confirmation in automated teardown
deployment_id: "{{ deployment_id | default('') }}"
redirector_name: "{{ redirector_name | default('r-' + deployment_id) }}"
c2_name: "{{ c2_name | default('s-' + deployment_id) }}"
tracker_name: "{{ tracker_name | default('t-' + deployment_id) }}"
cleanup_summary: {}
tasks:
# Confirmation step (if enabled)
- name: Confirm cleanup
pause:
prompt: "Are you sure you want to delete all AWS resources for deployment ID {{ deployment_id }}? This action cannot be undone. Type 'yes' to confirm"
register: cleanup_confirmation
when: confirm_cleanup | bool
- name: Check confirmation
assert:
that:
- cleanup_confirmation.user_input | default('yes') == 'yes'
fail_msg: "Cleanup cancelled by user"
when: confirm_cleanup | bool
# STEP 1: Find all instances by deployment ID
- name: Find all EC2 instances for this deployment
amazon.aws.ec2_instance_info:
region: "{{ aws_region }}"
filters:
"tag:deployment_id": "{{ deployment_id }}"
register: deployment_instances
- name: Set fact for instances found
set_fact:
cleanup_summary: "{{ cleanup_summary | combine({'instances_found': deployment_instances.instances | length}) }}"
# STEP 2: Terminate all instances with proper tagging
- name: Terminate all instances for this deployment
amazon.aws.ec2_instance:
instance_ids: "{{ item.instance_id }}"
region: "{{ aws_region }}"
state: absent
loop: "{{ deployment_instances.instances }}"
register: terminated_instances
when: deployment_instances.instances | length > 0
- name: Wait for instances to be terminated
pause:
seconds: 30
when: deployment_instances.instances | length > 0
# STEP 3: Find all security groups by deployment ID
- name: Find all security groups for this deployment
amazon.aws.ec2_security_group_info:
region: "{{ aws_region }}"
filters:
"tag:deployment_id": "{{ deployment_id }}"
register: deployment_sgs
# Also find SGs by name pattern
- name: Find security groups by name pattern
amazon.aws.ec2_security_group_info:
region: "{{ aws_region }}"
register: all_sgs
- name: Filter SGs by name pattern
set_fact:
named_sgs: "{{ all_sgs.security_groups | selectattr('group_name', 'search', redirector_name + '-sg|' + c2_name + '-sg') | list }}"
- name: Combine all security groups to delete
set_fact:
all_sgs_to_delete: "{{ deployment_sgs.security_groups + named_sgs }}"
cleanup_summary: "{{ cleanup_summary | combine({'security_groups_found': (deployment_sgs.security_groups + named_sgs) | length}) }}"
# STEP 4: Delete all security groups
- name: Delete security groups
amazon.aws.ec2_security_group:
group_id: "{{ item.group_id }}"
region: "{{ aws_region }}"
state: absent
loop: "{{ all_sgs_to_delete }}"
when: all_sgs_to_delete | length > 0
ignore_errors: yes
register: deleted_sgs
# STEP 5: Find and delete all ENIs
- name: Find network interfaces by tag
amazon.aws.ec2_eni_info:
region: "{{ aws_region }}"
filters:
"tag:deployment_id": "{{ deployment_id }}"
register: deployment_enis
- name: Delete ENIs
amazon.aws.ec2_eni:
region: "{{ aws_region }}"
eni_id: "{{ item.id }}"
state: absent
force_detach: true
loop: "{{ deployment_enis.network_interfaces }}"
ignore_errors: yes
register: deleted_enis
when: deployment_enis.network_interfaces | length > 0
- name: Set ENIs count in summary
set_fact:
cleanup_summary: "{{ cleanup_summary | combine({'enis_found': deployment_enis.network_interfaces | length}) }}"
# STEP 6: Find all VPCs by deployment ID
- name: Find all VPCs for this deployment
amazon.aws.ec2_vpc_net_info:
region: "{{ aws_region }}"
filters:
"tag:deployment_id": "{{ deployment_id }}"
register: deployment_vpcs
# STEP 7: Find VPCs by name pattern as fallback
- name: Find all VPCs by name pattern
amazon.aws.ec2_vpc_net_info:
region: "{{ aws_region }}"
register: all_vpcs
- name: Filter VPCs by name pattern
set_fact:
named_vpcs: "{{ all_vpcs.vpcs | selectattr('tags', 'defined') | selectattr('tags.Name', 'defined') | selectattr('tags.Name', 'search', redirector_name + '-vpc|' + c2_name + '-vpc') | list }}"
- name: Combine all VPCs to delete
set_fact:
all_vpcs_to_delete: "{{ deployment_vpcs.vpcs + named_vpcs | unique(attribute='vpc_id') }}"
cleanup_summary: "{{ cleanup_summary | combine({'vpcs_found': (deployment_vpcs.vpcs + named_vpcs | unique(attribute='vpc_id')) | length}) }}"
# STEP 8: Find and delete NAT Gateways for each VPC separately
- name: Find NAT gateways in each VPC
amazon.aws.ec2_vpc_nat_gateway_info:
region: "{{ aws_region }}"
filters:
vpc-id: "{{ item.vpc_id }}"
register: natgw_results
loop: "{{ all_vpcs_to_delete }}"
when: all_vpcs_to_delete | length > 0
- name: Delete NAT gateways
amazon.aws.ec2_vpc_nat_gateway:
region: "{{ aws_region }}"
nat_gateway_id: "{{ item.1.nat_gateway_id }}"
state: absent
release_eip: true
loop: "{{ natgw_results.results | default([]) | selectattr('skipped', 'undefined') | selectattr('nat_gateways', 'defined') | subelements('nat_gateways') }}"
ignore_errors: yes
register: deleted_natgws
when: natgw_results.results is defined
- name: Wait after NAT deletion
pause:
seconds: 15
when: deleted_natgws.results is defined and deleted_natgws.results | length > 0
# STEP 9: Find and delete Internet Gateways
- name: Find internet gateways for each VPC
amazon.aws.ec2_vpc_igw_info:
region: "{{ aws_region }}"
filters:
attachment.vpc-id: "{{ item.vpc_id }}"
register: igw_results
loop: "{{ all_vpcs_to_delete }}"
when: all_vpcs_to_delete | length > 0
- name: Detach and delete internet gateways
amazon.aws.ec2_vpc_igw:
internet_gateway_id: "{{ item.1.internet_gateway_id }}"
state: absent
region: "{{ aws_region }}"
loop: "{{ igw_results.results | default([]) | subelements('internet_gateways') }}"
ignore_errors: yes
register: deleted_igws
- name: Wait after IGW deletion
pause:
seconds: 15
when: deleted_igws.results is defined and deleted_igws.results | length > 0
# STEP 10: Find and delete Route Tables
- name: Find route tables for each VPC
amazon.aws.ec2_vpc_route_table_info:
region: "{{ aws_region }}"
filters:
vpc-id: "{{ item.vpc_id }}"
register: rtb_results
loop: "{{ all_vpcs_to_delete }}"
when: all_vpcs_to_delete | length > 0
- name: Delete non-main route tables
amazon.aws.ec2_vpc_route_table:
region: "{{ aws_region }}"
route_table_id: "{{ item.1.id }}"
lookup: id
state: absent
loop: "{{ rtb_results.results | default([]) | selectattr('skipped', 'undefined') | selectattr('route_tables', 'defined') | subelements('route_tables') }}"
when: not item.1.associations[0].main | default(false)
ignore_errors: yes
register: deleted_rtbs
# Add this after your existing route table deletion
- name: Delete main route tables with AWS CLI
shell: |
for rtb in $(aws ec2 describe-route-tables --region {{ aws_region }} --filters "Name=vpc-id,Values={{ item.vpc_id }}" --query 'RouteTables[?Associations[?Main==`true`]].RouteTableId' --output text); do
aws ec2 delete-route --route-table-id $rtb --destination-cidr-block 0.0.0.0/0 --region {{ aws_region }} || true
done
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
loop: "{{ all_vpcs_to_delete }}"
ignore_errors: yes
when: all_vpcs_to_delete | length > 0
# STEP 11: Find and delete Subnets
- name: Find subnets for each VPC
amazon.aws.ec2_vpc_subnet_info:
region: "{{ aws_region }}"
filters:
vpc-id: "{{ item.vpc_id }}"
loop: "{{ all_vpcs_to_delete }}"
register: subnet_results
- name: Delete subnets
amazon.aws.ec2_vpc_subnet:
region: "{{ aws_region }}"
vpc_id: "{{ item.1.vpc_id }}"
cidr: "{{ item.1.cidr_block }}"
state: absent
loop: "{{ subnet_results.results | default([]) | subelements('subnets') }}"
ignore_errors: yes
register: deleted_subnets
when: subnet_results.results is defined
# STEP 12: Find and delete VPC Endpoints
- name: Find VPC endpoints for each VPC
amazon.aws.ec2_vpc_endpoint_info:
region: "{{ aws_region }}"
filters:
vpc-id: "{{ item.vpc_id }}"
register: endpoint_results
loop: "{{ all_vpcs_to_delete }}"
when: all_vpcs_to_delete | length > 0
- name: Delete VPC endpoints
amazon.aws.ec2_vpc_endpoint:
region: "{{ aws_region }}"
vpc_endpoint_id: "{{ item.1.vpc_endpoint_id }}"
state: absent
loop: "{{ endpoint_results.results | default([]) | selectattr('skipped', 'undefined') | selectattr('vpc_endpoints', 'defined') | subelements('vpc_endpoints') }}"
ignore_errors: yes
register: deleted_endpoints
when: endpoint_results.results is defined
# Add before STEP 13
- name: Check for remaining VPC dependencies
shell: |
aws ec2 describe-network-interfaces --region {{ aws_region }} --filters "Name=vpc-id,Values={{ item.vpc_id }}" --output json
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
register: remaining_deps
loop: "{{ all_vpcs_to_delete }}"
when: all_vpcs_to_delete | length > 0
- name: Display any remaining dependencies
debug:
msg: "VPC {{ item.item.vpc_id }} still has dependencies that need to be removed"
loop: "{{ remaining_deps.results }}"
when: item.stdout | from_json | json_query('NetworkInterfaces') | length > 0
# Add this before the force delete of network interfaces
- name: Detach remaining network interfaces
shell: |
aws ec2 detach-network-interface --attachment-id $(aws ec2 describe-network-interfaces --network-interface-ids {{ item.1 }} --query 'NetworkInterfaces[0].Attachment.AttachmentId' --output text) --region {{ aws_region }} --force
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
loop: "{{ remaining_deps.results | selectattr('stdout', 'defined') |
map('attr', 'stdout') | map('from_json') |
map('json_query', 'NetworkInterfaces[?Status==`in-use`].NetworkInterfaceId') |
zip(remaining_deps.results | map('attr', 'item')) | list }}"
ignore_errors: yes
when: item.0 | length > 0
- name: Force delete any remaining network interfaces
shell: |
aws ec2 delete-network-interface --network-interface-id {{ item.1 }} --region {{ aws_region }}
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
loop: "{{ remaining_deps.results | selectattr('stdout', 'defined') |
map('attr', 'stdout') | map('from_json') |
map('json_query', 'NetworkInterfaces[].NetworkInterfaceId') |
zip(remaining_deps.results | map('attr', 'item')) | list }}"
ignore_errors: yes
when: item.0 | length > 0
# STEP 13: Final VPC deletion with multiple retries
- name: Wait for all dependencies to clear
pause:
seconds: 20
when: all_vpcs_to_delete | length > 0
# First attempt with normal module - with error display
- name: Delete all VPCs (first attempt)
amazon.aws.ec2_vpc_net:
vpc_id: "{{ item.vpc_id }}"
region: "{{ aws_region }}"
state: absent
loop: "{{ all_vpcs_to_delete }}"
register: vpc_deletion
when: all_vpcs_to_delete | length > 0
ignore_errors: yes
- name: Display VPC deletion errors
debug:
msg: "Failed to delete VPC {{ item.item.vpc_id }}: {{ item.msg }}"
loop: "{{ vpc_deletion.results | default([]) }}"
when: item.failed is defined and item.failed
# Direct API call for any VPCs that failed
- name: Find which VPCs still exist
amazon.aws.ec2_vpc_net_info:
region: "{{ aws_region }}"
vpc_ids: "{{ all_vpcs_to_delete | map(attribute='vpc_id') | list }}"
register: remaining_vpcs
when: all_vpcs_to_delete | length > 0
# Forcibly delete with direct AWS CLI command
- name: Force delete remaining VPCs with CLI
shell: |
aws ec2 delete-vpc --vpc-id {{ item.vpc_id }} --region {{ aws_region }}
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
loop: "{{ remaining_vpcs.vpcs }}"
ignore_errors: yes
when: remaining_vpcs is defined and remaining_vpcs.vpcs | length > 0
register: force_vpc_delete
# Add after the VPC deletion attempts - more aggressive approach
- name: Force delete remaining VPCs with AWS CLI and debug output
shell: |
aws ec2 delete-vpc --vpc-id {{ item.vpc_id }} --region {{ aws_region }} 2>&1 || echo "Failed with: $?"
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
loop: "{{ remaining_vpcs.vpcs }}"
register: force_vpc_delete_debug
when: remaining_vpcs is defined and remaining_vpcs.vpcs | length > 0
- name: Display debug output from force delete
debug:
msg: "{{ item.stdout }}"
loop: "{{ force_vpc_delete_debug.results | default([]) }}"
when: item.stdout is defined and item.stdout | trim != ""
# Track deleted VPCs in summary
- name: Set VPC deletion results in summary
set_fact:
cleanup_summary: "{{ cleanup_summary | combine({
'vpcs_deleted': ((vpc_deletion.results | default([]) | selectattr('failed', 'undefined') | list | length) + (force_vpc_delete.results | default([]) | selectattr('failed', 'undefined') | list | length))}) }}"
when: all_vpcs_to_delete | length > 0
# Add these tasks to confirm VPC deletion
- name: Verify VPC deletion
amazon.aws.ec2_vpc_net_info:
region: "{{ aws_region }}"
filters:
"tag:deployment_id": "{{ deployment_id }}"
register: vpc_check
- name: Display cleanup summary
debug:
msg:
- "Cleanup Summary:"
- "Redirector instance deleted: {{ redirector_deleted | default('N/A') }}"
- "C2 instance deleted: {{ c2_deleted | default('N/A') }}"
- "VPC resources deleted: {{ vpc_check.vpcs | length == 0 }}"
when: not disable_summary | default(false)
# STEP 14: Delete key pairs - completely revised implementation
- name: Check for deployment key in AWS
shell: |
aws ec2 describe-key-pairs --region {{ aws_region }} --filters "Name=key-name,Values=c2deploy_{{ deployment_id }}" --query "KeyPairs[*].KeyName" --output text
environment:
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
register: keypair_check
ignore_errors: yes
- name: Delete deployment key pair if it exists
amazon.aws.ec2_key:
name: "c2deploy_{{ deployment_id }}"
region: "{{ aws_region }}"
state: absent
when: keypair_check.stdout | trim != ""
register: deleted_keypair
- name: Delete SSH key files
file:
path: "{{ item }}"
state: absent
with_items:
- "~/.ssh/c2deploy_{{ deployment_id }}.pem"
- "~/.ssh/c2deploy_{{ deployment_id }}.pub"
- "~/.ssh/{{ redirector_name }}.pem"
- "~/.ssh/{{ c2_name }}.pem"
- "~/.ssh/{{ tracker_name }}.pem"
ignore_errors: yes
register: deleted_ssh_files
- name: Count deleted SSH files
set_fact:
cleanup_summary: "{{ cleanup_summary | combine({
'keypairs_deleted': (deleted_keypair.changed | default(false)) | ternary(1, 0),
'ssh_files_deleted': (deleted_ssh_files.results | selectattr('changed', 'defined') | selectattr('changed') | list | length)}) }}"
# Remove infrastructure state file - fix path to include deployment_id
- name: Remove infrastructure state file
file:
path: "infrastructure_state_{{ deployment_id }}.json"
state: absent
ignore_errors: yes
register: infra_file
- name: Clean up deployment state file
file:
path: "{{ playbook_dir }}/../deployment_state_{{ deployment_id }}.json"
state: absent
ignore_errors: yes
register: state_file_deletion
- name: Report state file cleanup
debug:
msg: "Deployment state file {{ 'deleted' if state_file_deletion.changed else 'not found' }}"
when: state_file_deletion is defined
# STEP 16: Enhanced and Accurate Cleanup Summary
- name: Enhanced cleanup summary
debug:
msg:
- "=========================================================="
- " AWS CLEANUP SUMMARY: {{ deployment_id }} "
- "=========================================================="
- "EC2 Instances: {{ cleanup_summary.instances_found | default(0) }} found, {{ terminated_instances.results | default([]) | length }} terminated"
- "Security Groups: {{ cleanup_summary.security_groups_found | default(0) }} found, {{ deleted_sgs.results | default([]) | length }} deleted"
- "Network Interfaces: {{ cleanup_summary.enis_found | default(0) }} found, {{ deleted_enis.results | default([]) | length }} deleted"
- "VPCs: {{ cleanup_summary.vpcs_found | default(0) }} found, {{ cleanup_summary.vpcs_deleted | default(0) }} deleted"
- "Key Pairs: {{ deleted_key_pairs.results | default([]) | length }} deleted"
- "SSH Key Files: {{ cleanup_summary.ssh_files_deleted | default(0) }} deleted"
- "Infrastructure file: {{ 'Removed' if infra_file.changed else 'Not found' }}"
- "=========================================================="
- "CLEANUP {{ 'COMPLETED' if (cleanup_summary.vpcs_deleted | default(0) == cleanup_summary.vpcs_found | default(0)) else 'PARTIAL - SOME RESOURCES MAY REMAIN' }}"
- "========================================================="