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.
This commit is contained in:
Operator
2026-06-23 16:12:14 -04:00
commit 0799bfbae8
239 changed files with 40012 additions and 0 deletions
+251
View File
@@ -0,0 +1,251 @@
---
# Linode Attack Box Deployment Playbook
# Based on attk-box-setup but optimized for headless server deployment
- name: Deploy Attack Box on Linode
hosts: localhost
connection: local
gather_facts: false
vars:
ansible_python_interpreter: "{{ ansible_playbook_python }}"
deployment_type: "attack_box"
attack_box_name: "{{ attack_box_name | default('a-' + deployment_id) }}"
attack_box_type: "{{ attack_box_type | default('kali') }}"
linode_instance_type: "{{ linode_instance_type | default('g6-standard-4') }}"
linode_region: "{{ linode_region | default('us-east') }}"
ssh_key_name: "{{ ssh_key_path | default('c2deploy_' + attack_box_name) | basename | regex_replace('\\.pub$', '') }}"
# Attack box image mapping (headless versions)
attack_box_images:
kali: "linode/kali"
custom: "linode/kali" # Use Kali as base for custom builds
quick_recon: "linode/kali" # Kali Linux for easy tool expansion
tasks:
- name: Debug attack box deployment
debug:
msg: "Deploying {{ attack_box_type }} attack box on Linode in {{ linode_region }}"
- name: Set attack box image
set_fact:
attack_box_image: "{{ attack_box_images[attack_box_type] | default('linode/kali') }}"
- name: Check if SSH key exists (should be pre-generated)
stat:
path: "~/.ssh/{{ ssh_key_name }}"
register: ssh_key_check
- name: Generate SSH key pair for attack box (if not exists)
openssh_keypair:
path: "~/.ssh/{{ ssh_key_name }}"
type: rsa
size: 2048
force: no
register: ssh_key_result
when: not ssh_key_check.stat.exists
- name: Read public key content
slurp:
src: "~/.ssh/{{ ssh_key_name }}.pub"
register: public_key_content
- name: Add SSH key to Linode
uri:
url: "https://api.linode.com/v4/profile/sshkeys"
method: POST
headers:
Authorization: "Bearer {{ linode_token }}"
Content-Type: "application/json"
body_format: json
body:
label: "{{ ssh_key_name }}"
ssh_key: "{{ public_key_content.content | b64decode | trim }}"
status_code: [200, 201]
register: linode_ssh_key
ignore_errors: yes
- name: Generate strong root password
set_fact:
root_password: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,!@#$%^&*-_=+ length=32') }}"
when: ansible_password is not defined
- name: Set ansible_password if not defined
set_fact:
ansible_password: "{{ root_password }}"
when: ansible_password is not defined and root_password is defined
- name: Create Linode instance for attack box
uri:
url: "https://api.linode.com/v4/linode/instances"
method: POST
headers:
Authorization: "Bearer {{ linode_token }}"
Content-Type: "application/json"
body_format: json
body:
label: "{{ attack_box_name }}"
type: "{{ linode_instance_type }}"
region: "{{ linode_region }}"
image: "{{ attack_box_image }}"
root_pass: "{{ ansible_password | default(root_password) }}"
authorized_keys:
- "{{ public_key_content.content | b64decode | trim }}"
booted: true
backups_enabled: false
private_ip: false
tags:
- "attack-box"
- "c2itall"
- "{{ deployment_id }}"
status_code: [200, 201]
register: linode_instance
- name: Get current timestamp
setup:
gather_subset: min
delegate_to: localhost
- name: Ensure logs directory exists
file:
path: "{{ playbook_dir }}/logs"
state: directory
mode: '0755'
delegate_to: localhost
- name: Save attack box credentials to local file
copy:
content: |
Attack Box: {{ attack_box_name }}
Deployment ID: {{ deployment_id }}
Instance IP: {{ linode_instance.json.ipv4[0] if linode_instance.json.ipv4 else 'Pending' }}
SSH Key: ~/.ssh/{{ ssh_key_name }}
Root Password: {{ ansible_password | default(root_password) }}
SSH Command: ssh -i ~/.ssh/{{ ssh_key_name }} root@{{ linode_instance.json.ipv4[0] if linode_instance.json.ipv4 else 'IP_ADDRESS' }}
Generated at: {{ ansible_date_time.iso8601 }}
dest: "{{ playbook_dir }}/logs/deployment_info_{{ deployment_id }}.txt"
mode: '0600'
when: linode_instance is succeeded
delegate_to: localhost
- name: Save instance information
set_fact:
attack_box_ip: "{{ linode_instance.json.ipv4[0] }}"
attack_box_id: "{{ linode_instance.json.id }}"
- name: Display attack box information
debug:
msg:
- "Attack Box Created Successfully!"
- "Name: {{ attack_box_name }}"
- "IP Address: {{ attack_box_ip }}"
- "Instance ID: {{ attack_box_id }}"
- "Type: {{ attack_box_type }}"
- "SSH Key: ~/.ssh/{{ ssh_key_name }}"
- name: Wait for instance to be fully booted
wait_for:
host: "{{ attack_box_ip }}"
port: 22
delay: 30
timeout: 300
ignore_errors: yes
- name: Test SSH connectivity
command: ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/{{ ssh_key_name }} root@{{ attack_box_ip }} echo "SSH connection successful"
register: ssh_test
retries: 5
delay: 30
until: ssh_test.rc == 0
ignore_errors: yes
- name: Create dynamic inventory for attack box configuration
add_host:
name: "{{ attack_box_ip }}"
groups: attack_boxes
ansible_host: "{{ attack_box_ip }}"
ansible_user: root
ansible_ssh_private_key_file: "~/.ssh/{{ ssh_key_name }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
attack_box_type: "{{ attack_box_type }}"
deployment_id: "{{ deployment_id }}"
- name: Save deployment information
copy:
content: |
# Attack Box Deployment Information
DEPLOYMENT_ID={{ deployment_id }}
ATTACK_BOX_NAME={{ attack_box_name }}
ATTACK_BOX_IP={{ attack_box_ip }}
ATTACK_BOX_ID={{ attack_box_id }}
ATTACK_BOX_TYPE={{ attack_box_type }}
SSH_KEY_PATH=~/.ssh/{{ ssh_key_name }}
PROVIDER=linode
REGION={{ linode_region }}
INSTANCE_TYPE={{ linode_instance_type }}
DEPLOYED_DATE={{ ansible_date_time.iso8601 }}
dest: "{{ playbook_dir }}/logs/attack_box_{{ deployment_id }}.env"
- name: Configure Attack Box
hosts: attack_boxes
gather_facts: yes
become: yes
vars:
setup_workspace: "{{ setup_workspace | default(true) }}"
setup_vpn: "{{ setup_vpn | default(false) }}"
setup_tor: "{{ setup_tor | default(false) }}"
tasks:
- name: Wait for system to be ready
wait_for_connection:
delay: 30
timeout: 300
- name: Update package cache only (avoid grub-pc issues)
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
retries: 3
delay: 10
- name: Include attack box configuration tasks
include_tasks: "{{ playbook_dir }}/../../modules/attack-box/tasks/configure_attack_box.yml"
when: deployment_type != "quick_recon_box"
- name: Include quick recon configuration tasks
include_tasks: "{{ playbook_dir }}/../../modules/attack-box/tasks/configure_quick_recon.yml"
when: deployment_type == "quick_recon_box"
- name: Display final setup information (Full Attack Box)
debug:
msg:
- "🎯 Attack Box Configuration Complete!"
- "📦 Instance: {{ attack_box_name }}"
- "🔑 SSH Command: ssh -i ~/.ssh/a-{{ deployment_id }} root@{{ ansible_host }}"
- "🔒 Root Password: {{ ansible_password | default('Check deployment_info_' + deployment_id + '.txt') }}"
- "📁 Credentials saved to: logs/deployment_info_{{ deployment_id }}.txt"
- ""
- "🛠️ Available Commands:"
- " recon <target> - Run reconnaissance automation"
- " portscan <target> - Run port scan automation"
- " webenum <target> - Run web enumeration automation"
when: deployment_type != "quick_recon_box"
- name: Display final setup information (Quick Recon Box)
debug:
msg:
- "🎯 Quick Recon Box Configuration Complete!"
- "📦 Instance: {{ attack_box_name }}"
- "🔑 SSH Command: ssh -i ~/.ssh/qr-{{ deployment_id }} root@{{ ansible_host }}"
- "🔒 Root Password: {{ ansible_password | default('Check deployment_info_' + deployment_id + '.txt') }}"
- "📁 Credentials saved to: logs/deployment_info_{{ deployment_id }}.txt"
- ""
- "🎯 Quick Recon Commands:"
- " qr - Go to working directory"
- " toolkit - Show available tools"
- " portscan <target> - Port scan"
- " subfind <domain> - Subdomain enumeration"
- " webscan <url> - Web application scan"
- " tor-recon <target> - Anonymous reconnaissance"
when: deployment_type == "quick_recon_box"
+205
View File
@@ -0,0 +1,205 @@
---
# Linode C2 Deployment Playbook
- name: Deploy Linode C2 server
hosts: localhost
gather_facts: false
connection: local
vars_files:
- vars.yaml
vars:
# Default values for required variables
linode_region: "{{ linode_region | default(region_choices | random) }}"
plan: "{{ plan | default('g6-standard-2') }}"
# Use static value to avoid recursive templating
c2_image_static: "linode/kali"
# Generate random instance name if not provided
c2_name: "{{ c2_name | default('node-' + 9999999999 | random | to_uuid | hash('md5') | truncate(8, True, '')) }}"
tasks:
- name: Validate required Linode token
assert:
that:
- linode_token is defined and linode_token != ""
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
# Replace these tasks at the beginning of the playbook
- name: Set region for C2 server
set_fact:
c2_region_value: "{{ selected_region | default(linode_region, true) | default('us-east', true) }}"
- name: Display region selection information for debugging
debug:
msg:
- "Selected Region: {{ selected_region | default('Not set') }}"
- "Linode Region: {{ linode_region | default('Not set') }}"
- "Using Region: {{ c2_region_value }}"
when: debug | default(false) | bool
- name: Set random region only if absolutely no region specified
set_fact:
c2_region_value: "{{ region_choices | random }}"
when: region_choices is defined and region_choices|length > 0 and not selected_region is defined and not linode_region is defined and not c2_region_value is defined
- name: Create C2 Linode instance
block:
- name: Try creating instance in specified region
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ c2_name }}"
type: "{{ plan }}"
region: "{{ c2_region_value }}"
image: "linode/kali"
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
authorized_keys:
- "{{ lookup('file', ssh_key_path) }}"
state: present
register: c2_instance
rescue:
- name: Log region restriction error
debug:
msg: "Region {{ c2_region_value }} is restricted. Trying fallback regions..."
- name: Set fallback regions (most reliable ones first)
set_fact:
fallback_regions:
- "us-east"
- "us-central"
- "eu-west"
- "us-west"
- name: Try fallback regions
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ c2_name }}"
type: "{{ plan }}"
region: "{{ item }}"
image: "linode/kali"
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
authorized_keys:
- "{{ lookup('file', ssh_key_path) }}"
state: present
register: c2_instance
loop: "{{ fallback_regions }}"
when: c2_instance is not defined or c2_instance.failed
ignore_errors: yes
- name: Update deployment region with successful fallback
set_fact:
c2_region_value: "{{ item }}"
loop: "{{ fallback_regions }}"
when: c2_instance.results is defined and c2_instance.results[ansible_loop.index0] is defined and not c2_instance.results[ansible_loop.index0].failed
- name: Fail if all regions are restricted
fail:
msg: "All attempted regions are restricted. Please try again later or contact Linode support."
when: c2_instance.failed | default(true)
- name: Set c2_ip for later use
block:
- name: Extract instance info from direct creation
set_fact:
c2_ip: "{{ c2_instance.instance.ipv4[0] }}"
c2_instance_id: "{{ c2_instance.instance.id }}"
when: c2_instance.instance is defined
- name: Extract instance info from fallback creation
set_fact:
c2_ip: "{{ item.instance.ipv4[0] }}"
c2_instance_id: "{{ item.instance.id }}"
loop: "{{ c2_instance.results | default([]) }}"
when: c2_instance.results is defined and item.instance is defined and not item.failed
- name: Display final C2 deployment region and IP
debug:
msg: "C2 server deployed successfully in region {{ c2_region_value }} with IP {{ c2_ip }}"
# Enhanced SSH wait task for Linode/c2.yml
- name: Wait for C2 SSH to be available
block:
- name: Initial wait for port to be open
wait_for:
host: "{{ c2_instance.instance.ipv4[0] }}"
port: 22
delay: 60
timeout: 180
state: started
- name: Additional pause for SSH initialization
pause:
seconds: 60
- name: Test SSH connection
command: >
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10
-i {{ ssh_key_path | replace('.pub', '') }} root@{{ c2_instance.instance.ipv4[0] }} echo "SSH Ready"
register: ssh_test
retries: 5
delay: 20
until: ssh_test.rc == 0
ignore_errors: yes
- name: Fail if SSH test unsuccessful
fail:
msg: "Could not connect to C2 server via SSH after multiple attempts"
when: ssh_test.rc != 0
- name: Add C2 to inventory
add_host:
name: "c2"
groups: "c2servers"
ansible_host: "{{ c2_ip }}"
ansible_user: "root"
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
ansible_python_interpreter: "/usr/bin/python3" # Use remote system's Python
- name: Configure C2 server
hosts: c2servers
become: true
gather_facts: true
vars_files:
- vars.yaml
vars:
redirector_ip: "{{ hostvars['localhost']['redirector_ip'] | default('127.0.0.1') }}"
c2_subdomain: "{{ c2_subdomain | default('mail') }}"
tasks:
- name: Wait for apt to be available
apt:
update_cache: yes
register: apt_result
until: apt_result is success
retries: 5
delay: 10
- name: Disable root password authentication for SSH immediately
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
- name: Restart SSH service to apply changes
service:
name: ssh
state: restarted
- name: Include common tool installation tasks
include_tasks: "../../common/tasks/install_tools.yml"
- name: Include common C2 configuration tasks
include_tasks: "../../modules/c2/tasks/configure_c2.yml"
- name: Include common mail server configuration tasks
include_tasks: "../../common/tasks/configure_mail.yml"
- name: Print deployment summary
debug:
msg:
- "C2 Server Deployment Complete!"
- "-----------------------------"
- "C2 Server IP: {{ ansible_host }}"
- "C2 Server Domain: {{ domain }}"
- "GoPhish Admin Port: {{ gophish_admin_port }}"
when: not disable_summary | default(false)
+117
View File
@@ -0,0 +1,117 @@
---
# Linode/cleanup.yml
- name: Clean up Linode infrastructure
hosts: localhost
connection: local
gather_facts: false
vars_files:
- vars.yaml
vars:
cleanup_redirector: "{{ (redirector_name is defined and redirector_name != '') | ternary(true, false) }}"
cleanup_c2: "{{ (c2_name is defined and c2_name != '') | ternary(true, false) }}"
cleanup_tracker: "{{ (tracker_name is defined and tracker_name != '') | ternary(true, false) }}"
cleanup_attack_box: "{{ (attack_box_name is defined and attack_box_name != '') | ternary(true, false) }}"
confirm_cleanup: false # Skip confirmation in automated teardown
tasks:
- name: Validate required Linode token
assert:
that:
- linode_token is defined and linode_token != ""
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
- name: Cleanup resources notice
debug:
msg: |
===============================================
CLEANUP OPERATION
===============================================
The following resources will be DELETED PERMANENTLY:
{% if cleanup_redirector and redirector_name is defined %}
- Redirector instance: {{ redirector_name }}
{% endif %}
{% if cleanup_c2 and c2_name is defined %}
- C2 instance: {{ c2_name }}
{% endif %}
{% if cleanup_tracker and tracker_name is defined %}
- Tracker instance: {{ tracker_name }}
{% endif %}
{% if cleanup_attack_box and attack_box_name is defined %}
- Attack Box instance: {{ attack_box_name }}
{% endif %}
when: confirm_cleanup | bool
- name: Confirm cleanup operation
pause:
prompt: "\n>>> Type 'yes' to confirm deletion or press Ctrl+C to abort <<<"
register: confirmation
when: confirm_cleanup | bool
- name: Skip cleanup if not confirmed
meta: end_play
when: confirm_cleanup | bool and (confirmation.user_input | default('')) != 'yes'
- name: Delete redirector instance
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ redirector_name }}"
state: absent
when: cleanup_redirector and redirector_name is defined and redirector_name != ""
register: redirector_deletion
ignore_errors: yes
- name: Delete C2 instance
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ c2_name }}"
state: absent
when: cleanup_c2 and c2_name is defined and c2_name != ""
register: c2_deletion
ignore_errors: yes
- name: Delete tracker instance
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ tracker_name }}"
state: absent
when: cleanup_tracker and tracker_name is defined and tracker_name != ""
register: tracker_deletion
ignore_errors: yes
- name: Delete attack box instance
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ attack_box_name }}"
state: absent
when: cleanup_attack_box and attack_box_name is defined and attack_box_name != ""
register: attack_box_deletion
ignore_errors: yes
- 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
- name: Report cleanup status
debug:
msg: |
Cleanup results:
{% if redirector_deletion is defined and redirector_name is defined %}
- Redirector {{ redirector_name }}: {{ redirector_deletion.changed | ternary('Deleted', 'Not found/Could not delete') }}
{% endif %}
{% if c2_deletion is defined and c2_name is defined %}
- C2 {{ c2_name }}: {{ c2_deletion.changed | ternary('Deleted', 'Not found/Could not delete') }}
{% endif %}
{% if tracker_deletion is defined and tracker_name is defined %}
- Tracker {{ tracker_name }}: {{ tracker_deletion.changed | ternary('Deleted', 'Not found/Could not delete') }}
{% endif %}
{% if attack_box_deletion is defined and attack_box_name is defined %}
- Attack Box {{ attack_box_name }}: {{ attack_box_deletion.changed | ternary('Deleted', 'Not found/Could not delete') }}
{% endif %}
+120
View File
@@ -0,0 +1,120 @@
---
# Linode-specific phishing infrastructure tasks
- name: Create Linode instances for phishing
uri:
url: "https://api.linode.com/v4/linode/instances"
method: POST
headers:
Authorization: "Bearer {{ linode_api_token }}"
Content-Type: "application/json"
body_format: json
body:
type: "{{ linode_instance_type | default('g6-nanode-1') }}"
region: "{{ linode_region | default('us-east') }}"
image: "{{ linode_image | default('linode/ubuntu22.04') }}"
label: "{{ deployment_id }}-{{ item }}"
root_pass: "{{ instance_password }}"
authorized_keys:
- "{{ ssh_public_key }}"
tags:
- "c2itall"
- "phishing"
- "{{ deployment_id }}"
status_code: 200
register: linode_instances
loop: "{{ deployment_components }}"
when: item in deployment_components
- name: Wait for instances to be running
uri:
url: "https://api.linode.com/v4/linode/instances/{{ item.json.id }}"
method: GET
headers:
Authorization: "Bearer {{ linode_api_token }}"
register: instance_status
until: instance_status.json.status == "running"
retries: 30
delay: 10
loop: "{{ linode_instances.results }}"
when: linode_instances.results is defined
- name: Get instance details
uri:
url: "https://api.linode.com/v4/linode/instances/{{ item.json.id }}"
method: GET
headers:
Authorization: "Bearer {{ linode_api_token }}"
register: instance_details
loop: "{{ linode_instances.results }}"
when: linode_instances.results is defined
- name: Set instance facts
set_fact:
phishing_instances: >-
{% set instances = [] %}
{% for result in instance_details.results %}
{% set instance = {
'id': result.json.id,
'label': result.json.label,
'ipv4': result.json.ipv4[0],
'region': result.json.region,
'type': result.json.type,
'status': result.json.status
} %}
{% set _ = instances.append(instance) %}
{% endfor %}
{{ instances }}
when: instance_details.results is defined
- name: Wait for SSH connectivity
wait_for:
host: "{{ item.ipv4 }}"
port: 22
delay: 30
timeout: 300
loop: "{{ phishing_instances }}"
when: phishing_instances is defined
- name: Update instance inventory
add_host:
name: "{{ item.ipv4 }}"
groups: "phishing_{{ item.label.split('-')[-1] }}"
ansible_host: "{{ item.ipv4 }}"
ansible_user: root
ansible_ssh_private_key_file: "{{ ssh_private_key_path }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
instance_id: "{{ item.id }}"
instance_label: "{{ item.label }}"
provider: "linode"
loop: "{{ phishing_instances }}"
when: phishing_instances is defined
- name: Configure Gophish servers
include_tasks: ../common/configure_gophish.yml
when: "'gophish' in deployment_components"
- name: Configure MTA fronts
include_tasks: ../common/configure_mta.yml
when: "'mta_front' in deployment_components"
- name: Configure redirectors
include_tasks: ../common/configure_redirector.yml
when: "'redirector' in deployment_components"
- name: Configure web servers
include_tasks: ../common/configure_webserver.yml
when: "'webserver' in deployment_components"
- name: Display deployment summary
debug:
msg: |
🎯 Linode Phishing Infrastructure Deployed
{{ phishing_instances | length }} instances created
Provider: Linode
Deployment ID: {{ deployment_id }}
Instances:
{% for instance in phishing_instances %}
- {{ instance.label }}: {{ instance.ipv4 }} ({{ instance.type }})
{% endfor %}
when: phishing_instances is defined
+65
View File
@@ -0,0 +1,65 @@
---
# Phishing infrastructure deployment playbook
# This is a comprehensive playbook that handles all phishing components
- name: Deploy Phishing Infrastructure
hosts: localhost
gather_facts: true
connection: local
vars_files:
- vars.yaml
tasks:
- name: Display deployment information
debug:
msg: |
Deploying phishing infrastructure
Deployment ID: {{ deployment_id }}
Provider: {{ provider }}
Domain: {{ domain | default(phishing_domain) }}
Components: {{ deployment_type }}
- name: Set deployment facts
set_fact:
deployment_timestamp: "{{ ansible_date_time.epoch }}"
phishing_deployment_results: {}
deployment_components: >-
{% set components = [] %}
{% if deployment_type in ['gophish_only', 'basic_phishing', 'advanced_phishing', 'full_phishing', 'fedramp_phishing'] %}
{% set _ = components.append('gophish') %}
{% endif %}
{% if deployment_type in ['mta_front_only', 'basic_phishing', 'advanced_phishing', 'full_phishing', 'ephemeral_mta'] %}
{% set _ = components.append('mta_front') %}
{% endif %}
{% if deployment_type in ['phishing_redirector_only', 'advanced_phishing', 'full_phishing'] %}
{% set _ = components.append('redirector') %}
{% endif %}
{% if deployment_type in ['phishing_webserver_only', 'full_phishing', 'fedramp_phishing'] %}
{% set _ = components.append('webserver') %}
{% endif %}
{{ components }}
- name: Include provider-specific tasks
include_tasks: "{{ provider }}_phishing.yml"
when: deployment_components | length > 0
- name: Ensure logs directory exists
file:
path: "../../logs"
state: directory
mode: '0755'
delegate_to: localhost
- name: Save deployment information
template:
src: phishing_deployment_info.j2
dest: "{{ playbook_dir }}/logs/phishing_deployment_{{ deployment_id }}.json"
delegate_to: localhost
- name: Display success message
debug:
msg: |
✅ Phishing infrastructure deployment completed
Deployment ID: {{ deployment_id }}
Components deployed: {{ deployment_components | join(', ') }}
Check logs/phishing_deployment_{{ deployment_id }}.json for details
+190
View File
@@ -0,0 +1,190 @@
---
# Linode Redirector-only Deployment Playbook
- name: Deploy Linode redirector
hosts: localhost
gather_facts: false
connection: local
vars_files:
- vars.yaml
vars:
# Use constant values directly to avoid recursive template resolution
redirector_image_static: "linode/debian12"
tasks:
- name: Validate required Linode token
assert:
that:
- linode_token is defined and linode_token != ""
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
# Update the region selection logic in Linode/redirector.yml
- name: Set region for redirector
set_fact:
deployment_region: "{{ selected_region | default(linode_region, true) | default('us-east', true) }}"
- name: Display region selection for redirector
debug:
msg: "Using region for redirector: {{ deployment_region }}"
when: debug | default(false) | bool
# Only use random region as a last resort
- name: Set random region only if no region specified
set_fact:
deployment_region: "{{ region_choices | random }}"
when: region_choices is defined and region_choices|length > 0 and not selected_region is defined and not linode_region is defined and not deployment_region is defined
- name: Create redirector Linode instance
block:
- name: Try creating instance in specified region
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ redirector_name }}"
type: "{{ redirector_plan | default('g6-nanode-1') }}"
region: "{{ deployment_region }}"
image: "linode/debian12"
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
authorized_keys:
- "{{ lookup('file', ssh_key_path) }}"
state: present
register: redirector_instance
rescue:
- name: Log region restriction error
debug:
msg: "Region {{ deployment_region }} is restricted. Trying fallback regions..."
- name: Set fallback regions (most reliable ones first)
set_fact:
fallback_regions:
- "us-east"
- "us-central"
- "eu-west"
- "us-west"
- name: Try fallback regions
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ redirector_name }}"
type: "{{ redirector_plan | default('g6-nanode-1') }}"
region: "{{ item }}"
image: "linode/debian12"
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
authorized_keys:
- "{{ lookup('file', ssh_key_path) }}"
state: present
register: redirector_instance
loop: "{{ fallback_regions }}"
when: redirector_instance is not defined or redirector_instance.failed
ignore_errors: yes
- name: Update deployment region with successful fallback
set_fact:
deployment_region: "{{ item }}"
loop: "{{ fallback_regions }}"
when: redirector_instance.results is defined and redirector_instance.results[ansible_loop.index0] is defined and not redirector_instance.results[ansible_loop.index0].failed
- name: Fail if all regions are restricted
fail:
msg: "All attempted regions are restricted. Please try again later or contact Linode support."
when: redirector_instance.failed | default(true)
- name: Set redirector_ip for later use
block:
- name: Extract instance info from direct creation
set_fact:
redirector_ip: "{{ redirector_instance.instance.ipv4[0] }}"
redirector_instance_id: "{{ redirector_instance.instance.id }}"
when: redirector_instance.instance is defined
- name: Extract instance info from fallback creation
set_fact:
redirector_ip: "{{ item.instance.ipv4[0] }}"
redirector_instance_id: "{{ item.instance.id }}"
loop: "{{ redirector_instance.results | default([]) }}"
when: redirector_instance.results is defined and item.instance is defined and not item.failed
- name: Display final deployment region and IP
debug:
msg: "Redirector deployed successfully in region {{ deployment_region }} with IP {{ redirector_ip }}"
# Enhanced SSH wait task with better retry mechanism
- name: Wait for redirector SSH to be available
block:
- name: Initial wait for port to be open
wait_for:
host: "{{ redirector_instance.instance.ipv4[0] }}"
port: 22
delay: 60
timeout: 180
state: started
- name: Additional pause for SSH initialization
pause:
seconds: 60
- name: Test SSH connection
command: >
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10
-i {{ ssh_key_path | replace('.pub', '') }} root@{{ redirector_instance.instance.ipv4[0] }} echo "SSH Ready"
register: ssh_test
retries: 5
delay: 20
until: ssh_test.rc == 0
ignore_errors: yes
- name: Fail if SSH test unsuccessful
fail:
msg: "Could not connect to redirector via SSH after multiple attempts"
when: ssh_test.rc != 0
- name: Add redirector to inventory
add_host:
name: "redirector"
groups: "redirectors"
ansible_host: "{{ redirector_ip }}"
ansible_user: "root"
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
- name: Configure redirector server
hosts: redirectors
become: true
gather_facts: true
vars_files:
- vars.yaml
vars:
c2_ip: "{{ hostvars['localhost']['c2_ip'] | default('127.0.0.1') }}"
redirector_subdomain: "{{ redirector_subdomain | default('cdn') }}"
ansible_python_interpreter: auto # Explicitly add this line
tasks:
- name: Wait for apt to be available
apt:
update_cache: yes
register: apt_result
until: apt_result is success
retries: 5
delay: 10
- name: Disable root password authentication for SSH immediately
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
- name: Restart SSH service to apply changes
service:
name: ssh
state: restarted
- name: Include common redirector configuration tasks
include_tasks: "../../modules/redirectors/tasks/configure_redirector.yml"
- name: Print deployment summary
debug:
msg:
- "Redirector Deployment Complete!"
- "-------------------------------"
- "Redirector IP: {{ ansible_host }}"
- "Redirector Domain: {{ redirector_subdomain }}.{{ domain }} (Update DNS A record)"
when: not disable_summary | default(false)
@@ -0,0 +1,10 @@
{
"deployment_id": "{{ deployment_id }}",
"deployment_type": "{{ deployment_type }}",
"provider": "{{ provider }}",
"domain": "{{ phishing_domain | default(domain) }}",
"timestamp": "{{ ansible_date_time.iso8601 }}",
"components": {{ deployment_components | to_json }},
"results": {{ phishing_deployment_results | default({}) | to_json }},
"status": "completed"
}
+152
View File
@@ -0,0 +1,152 @@
---
# Linode Tracker Deployment Playbook
- name: Deploy Linode email tracking server
hosts: localhost
gather_facts: false
connection: local
vars_files:
- vars.yaml
vars:
# Default values - using minimalist settings
linode_region: "{{ linode_region | default(region_choices | random) }}"
plan: "{{ plan | default('g6-nanode-1') }}" # Use smallest plan for tracker
tracker_image: "{{ image | default('linode/debian12') }}" # Use Debian instead of Kali
# Generate random instance name if not provided
tracker_name: "{{ tracker_name | default('t-' + 9999999999 | random | to_uuid | hash('md5') | truncate(8, True, '')) }}"
tasks:
- name: Validate required Linode token
assert:
that:
- linode_token is defined and linode_token != ""
fail_msg: "Linode API token is required. Set linode_token in vars.yaml."
- name: Set region for tracker
set_fact:
tracker_region_value: "{{ selected_region | default(region, true) | default(linode_region, true) | default('us-east', true) }}"
when: region_choices is not defined or region_choices|length == 0
- name: Set random region for tracker
set_fact:
tracker_region_value: "{{ region_choices | random }}"
when: region_choices is defined and region_choices|length > 0 and tracker_region_value is not defined
- name: Create tracker Linode instance
community.general.linode_v4:
access_token: "{{ linode_token }}"
label: "{{ tracker_name }}"
type: "{{ plan }}"
region: "{{ tracker_region_value }}"
image: "{{ image | default('linode/debian12') }}"
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
authorized_keys:
- "{{ lookup('file', ssh_key_path) }}"
state: present
register: tracker_instance
- name: Set tracker_ip for later use
set_fact:
tracker_ip: "{{ tracker_instance.instance.ipv4[0] }}"
tracker_instance_id: "{{ tracker_instance.instance.id }}"
- name: Wait for tracker SSH to be available
wait_for:
host: "{{ tracker_ip }}"
port: 22
delay: 60
timeout: 300
state: started
- name: Add tracker to inventory
add_host:
name: "tracker"
groups: "trackers"
ansible_host: "{{ tracker_ip }}"
ansible_user: "root"
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
- name: Configure tracker server
hosts: trackers
become: true
gather_facts: true
vars_files:
- vars.yaml
vars:
tracker_domain: "{{ tracker_domain | default('track.' + domain) }}"
tasks:
- name: Update and install minimal required packages
apt:
name:
- nginx
- certbot
- python3-certbot-nginx
- git
- python3-pip
- python3-venv
update_cache: yes
state: present
- name: Clone email tracker repository
git:
repo: https://github.com/Datalux/Osint-Tracker.git
dest: /root/Tools/tracker
version: master
ignore_errors: yes
- name: Install tracker dependencies
pip:
requirements: /root/Tools/tracker/requirements.txt
virtualenv: /root/Tools/tracker/venv
virtualenv_command: python3 -m venv
ignore_errors: yes
- name: Configure tracker settings
template:
src: "../../modules/tracker/templates/tracker-config.j2"
dest: /root/Tools/tracker/config.py
mode: '0644'
ignore_errors: yes
- name: Set up SSL if requested
block:
- name: Install Let's Encrypt certificate
shell: |
certbot --nginx -d {{ tracker_domain }} --non-interactive --agree-tos -m {{ tracker_email }}
args:
creates: /etc/letsencrypt/live/{{ tracker_domain }}/fullchain.pem
when: tracker_setup_ssl | default(true) | bool
ignore_errors: yes
- name: Create systemd service for tracker
template:
src: "../../modules/tracker/templates/tracker.service.j2"
dest: /etc/systemd/system/tracker.service
mode: '0644'
ignore_errors: yes
- name: Update tracker service to use virtualenv
blockinfile:
path: /etc/systemd/system/tracker.service
insertafter: "^\\[Service\\]"
block: |
Environment="PATH=/root/Tools/tracker/venv/bin:$PATH"
ExecStart=/root/Tools/tracker/venv/bin/python /root/Tools/tracker/app.py
- name: Start and enable tracker service
systemd:
name: tracker
state: started
enabled: yes
daemon_reload: yes
ignore_errors: yes
- name: Print deployment summary
debug:
msg:
- "Email Tracker Deployment Complete!"
- "------------------------------"
- "Tracker IP: {{ ansible_host }}"
- "Tracker Domain: {{ tracker_domain }} (Update DNS A record)"
@@ -0,0 +1,90 @@
---
# Linode provision tasks for one WEBRUNNER node
# Called in a loop — loop_var: node_chunk
# Requires: linode_token, ssh_key_name, webrunner_name, deployment_id,
# linode_instance_type, linode_region, scanner_ip_log, results_dir
- name: Generate root password for {{ node_chunk.node_name }}
set_fact:
wr_root_pass: "{{ lookup('password', '/dev/null chars=ascii_letters,digits length=32') }}"
- name: Read public key
slurp:
src: "~/.ssh/{{ ssh_key_name }}.pub"
register: wr_pubkey
- name: Register SSH key in Linode ({{ node_chunk.node_name }})
uri:
url: "https://api.linode.com/v4/profile/sshkeys"
method: POST
headers:
Authorization: "Bearer {{ linode_token }}"
Content-Type: "application/json"
body_format: json
body:
label: "{{ webrunner_name }}"
ssh_key: "{{ wr_pubkey.content | b64decode | trim }}"
status_code: [200, 201]
ignore_errors: true
- name: Create Linode instance {{ node_chunk.node_name }}
uri:
url: "https://api.linode.com/v4/linode/instances"
method: POST
headers:
Authorization: "Bearer {{ linode_token }}"
Content-Type: "application/json"
body_format: json
body:
label: "{{ node_chunk.node_name }}"
type: "{{ linode_instance_type | default('g6-standard-2') }}"
region: "{{ linode_region | default('us-east') }}"
image: "linode/debian12"
root_pass: "{{ wr_root_pass }}"
authorized_keys:
- "{{ wr_pubkey.content | b64decode | trim }}"
booted: true
backups_enabled: false
private_ip: false
tags:
- "webrunner"
- "{{ webrunner_name }}"
- "{{ deployment_id }}"
status_code: [200, 201]
register: wr_linode
- name: Extract node IP
set_fact:
wr_node_ip: "{{ wr_linode.json.ipv4[0] }}"
- name: Log scanner IP
lineinfile:
path: "{{ scanner_ip_log }}"
line: "{{ node_chunk.node_name }}: {{ wr_node_ip }}"
create: true
- name: Wait for SSH on {{ node_chunk.node_name }} ({{ wr_node_ip }})
wait_for:
host: "{{ wr_node_ip }}"
port: 22
delay: 30
timeout: 300
- name: Add {{ node_chunk.node_name }} to inventory
add_host:
name: "{{ wr_node_ip }}"
groups: webrunner_nodes
ansible_host: "{{ wr_node_ip }}"
ansible_user: root
ansible_ssh_private_key_file: "~/.ssh/{{ ssh_key_name }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
node_name: "{{ node_chunk.node_name }}"
node_cidrs: "{{ node_chunk.cidrs }}"
node_ip_count: "{{ node_chunk.ip_count }}"
node_idx: "{{ node_chunk.idx }}"
linode_instance_id: "{{ wr_linode.json.id }}"
provider: linode
- name: Show {{ node_chunk.node_name }} ready
debug:
msg: "Linode node ready: {{ node_chunk.node_name }} @ {{ wr_node_ip }} ({{ node_chunk.ip_count | int | string }} IPs)"
@@ -0,0 +1,90 @@
---
# Linode provision tasks for one WEBRUNNER node
# Called in a loop — loop_var: node_chunk
# Requires: linode_token, ssh_key_name, webrunner_name, deployment_id,
# linode_instance_type, linode_region, scanner_ip_log, results_dir
- name: Generate root password for {{ node_chunk.node_name }}
set_fact:
wr_root_pass: "{{ lookup('password', '/dev/null chars=ascii_letters,digits length=32') }}"
- name: Read public key
slurp:
src: "~/.ssh/{{ ssh_key_name }}.pub"
register: wr_pubkey
- name: Register SSH key in Linode ({{ node_chunk.node_name }})
uri:
url: "https://api.linode.com/v4/profile/sshkeys"
method: POST
headers:
Authorization: "Bearer {{ linode_token }}"
Content-Type: "application/json"
body_format: json
body:
label: "{{ webrunner_name }}"
ssh_key: "{{ wr_pubkey.content | b64decode | trim }}"
status_code: [200, 201]
ignore_errors: true
- name: Create Linode instance {{ node_chunk.node_name }}
uri:
url: "https://api.linode.com/v4/linode/instances"
method: POST
headers:
Authorization: "Bearer {{ linode_token }}"
Content-Type: "application/json"
body_format: json
body:
label: "{{ node_chunk.node_name }}"
type: "{{ linode_instance_type | default('g6-standard-2') }}"
region: "{{ linode_region | default('us-east') }}"
image: "linode/debian12"
root_pass: "{{ wr_root_pass }}"
authorized_keys:
- "{{ wr_pubkey.content | b64decode | trim }}"
booted: true
backups_enabled: false
private_ip: false
tags:
- "webrunner"
- "{{ webrunner_name }}"
- "{{ deployment_id }}"
status_code: [200, 201]
register: wr_linode
- name: Extract node IP
set_fact:
wr_node_ip: "{{ wr_linode.json.ipv4[0] }}"
- name: Log scanner IP
lineinfile:
path: "{{ scanner_ip_log }}"
line: "{{ node_chunk.node_name }}: {{ wr_node_ip }}"
create: true
- name: Wait for SSH on {{ node_chunk.node_name }} ({{ wr_node_ip }})
wait_for:
host: "{{ wr_node_ip }}"
port: 22
delay: 30
timeout: 300
- name: Add {{ node_chunk.node_name }} to inventory
add_host:
name: "{{ wr_node_ip }}"
groups: webrunner_nodes
ansible_host: "{{ wr_node_ip }}"
ansible_user: root
ansible_ssh_private_key_file: "~/.ssh/{{ ssh_key_name }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
node_name: "{{ node_chunk.node_name }}"
node_cidrs: "{{ node_chunk.cidrs }}"
node_ip_count: "{{ node_chunk.ip_count }}"
node_idx: "{{ node_chunk.idx }}"
linode_instance_id: "{{ wr_linode.json.id }}"
provider: linode
- name: Show {{ node_chunk.node_name }} ready
debug:
msg: "Linode node ready: {{ node_chunk.node_name }} @ {{ wr_node_ip }} ({{ node_chunk.ip_count | int | string }} IPs)"